#include "../liolib/lioflat.h"

namespace Lio {

#define DEF_POINTS 100
#define GRAPH_POS_Y 120
#define GRAPH_MAR_Y 80
#define GRAPH_MAX_Y 0.6
#define GRAPH_MIN_Y -0.6

enum ids {
	ID_T,
	ID_X,
	ID_Y,
	ID_LSM,
	ID_ERR,
	ID_SIZE,
	ID_F,
	ID_COUNT
};

static double x=0,y=0,t=0;
static int N = 0, n = 0;

//--------------------------------------------------------------
// FUNCTIONS
//--------------------------------------------------------------

/* ELIPSE
#define DEF_START 0
#define DEF_END 360
double phi = 0, R1 = 1, R2 = 1;
double cosp,sinp;
inline void func() // called on each argument update
{
	double X0=R1*cos(DEG_TO_RAD*t);
	double Y0=R2*sin(DEG_TO_RAD*t);
	x=X0*cosp+Y0*sinp;
	y=X0*sinp-Y0*cosp;
}
void getParams(InitInfo &params)
{
	phi=params.getDouble("phi",phi);
	R1=params.getDouble("R1",R1);
	R2=params.getDouble("R2",R2);

	cosp=cos(DEG_TO_RAD*phi);
	sinp=sin(DEG_TO_RAD*phi);
}
void addParams(InitInfo &params)
{
	params.format.precision=2;
	params.format.exposant=FALSE;

	params.addDouble("phi",phi);
	params.addDouble("R1",R1);
	params.addDouble("R2",R2);
}
*/
/* sqrt(parabola)
#define DEF_START -1
#define DEF_END 1
double a = 1, b = -2, c = 1;
inline void func() // called on each argument update
{
	x=t;
	y= (1 - x/a) * x;
}
void getParams(InitInfo &params)
{
	a=params.getDouble("a",a);
	b=params.getDouble("b",b);
	c=params.getDouble("c",c);
}
void addParams(InitInfo &params)
{
	params.format.precision=2;
	params.format.exposant=FALSE;
	params.addDouble("a",a);
	params.addDouble("b",b);
	params.addDouble("c",c);
}
/**/
/*
#define DEF_START 0
#define DEF_END 360
double X1 = 1, Y1 = 0, X0 = 0, Y0 = 0, Rx = 1, Ry = 1, phi = 0;
double cosp,sinp;
inline void func() // called on each argument update
{
	x=t;
	double a=DEG_TO_RAD*t;
	double c=cos(a);
	double s=sin(a);
	double xe=X0+Rx*c;
	double ye=Y0+Ry*s;
	double dx=xe*cosp+ye*sinp-X1;
	double dy=xe*sinp-ye*cosp-Y1;
	double dxdt=-Rx*s*cosp+Ry*c*sinp;
	double dydt=-Rx*s*sinp-Ry*c*cosp;
	y=abs(dx*dxdt+dy*dydt);
}
void getParams(InitInfo &params)
{
	phi=params.getDouble("phi",phi);
	Rx=params.getDouble("Rx",Rx);
	Ry=params.getDouble("Ry",Ry);
	X0=params.getDouble("X0",X0);
	Y0=params.getDouble("Y0",Y0);
	X1=params.getDouble("X1",X1);
	Y1=params.getDouble("Y1",Y1);

	cosp=cos(DEG_TO_RAD*phi);
	sinp=sin(DEG_TO_RAD*phi);
}
void addParams(InitInfo &params)
{
	params.format.precision=2;
	params.format.exposant=FALSE;

	params.addDouble("phi",phi);
	params.addDouble("Rx",Rx);
	params.addDouble("Ry",Ry);
	params.addDouble("X0",X0);
	params.addDouble("Y0",Y0);
	params.addDouble("X1",X1);
	params.addDouble("Y1",Y1);
}
/**/
//* --------------------------------- SPLINES
struct Point
{
	float x,y;
	Point():x(0),y(0){}
	Point(float x, float y):x(x),y(y){}
	Point operator *(float k) const {return Point(x*k, y*k);}
	Point operator /(float k) const {return Point(x/k, y/k);}
	Point operator -(const Point &p) const {return Point(x - p.x, y - p.y);}
	Point operator +(const Point &p) const {return Point(x + p.x, y + p.y);}
	float DistanceTo2D(const Point &p) const {return sqrtf(pow2(x - p.x) + pow2(y - p.y));}
};
static const int BS3RANGE = 4096;
static const float INV_BS3RANGE = 1.0f/BS3RANGE;
#define DEF_START 0
#define DEF_END BS3RANGE
inline void GetSplineCoefs( const Point * spline, Point * coefs )
{
	Point
		c0 = spline[0],
		c1 = spline[1]*3 - spline[0]*3,
		c2 = spline[2]*3 - spline[1]*6 + spline[0]*3,
		c3 = spline[3] - spline[2]*3 + spline[1]*3 - spline[0];
	coefs[0] = c0;
	coefs[1] = c1;
	coefs[2] = c2;
	coefs[3] = c3;
}
inline Point EvalSpline( const Point * coefs, float k)
{
	return ( ( coefs[3] * k + coefs[2] ) * k + coefs[1] ) * k + coefs[0];
}
static Point spline[4], coefs[4];
static float len = 0;
static Point last_pt;
static int i = 0;
inline void func()
{
	x = t;
	if( !n )
	{
		y = len = 0;
		last_pt = coefs[0];
	}
	else
	{
		Point pt = EvalSpline( coefs, float(n)/(N-1) );
		len += pt.DistanceTo2D(last_pt);
		y = len;
		last_pt = pt;
	}
}
void getParams(InitInfo &params)
{
	spline[0].x=params.getDouble("s0x",0);
	spline[0].y=params.getDouble("s0y",0);
	
	spline[1].x=params.getDouble("s1x",1);
	spline[1].y=params.getDouble("s1y",1);

	spline[2].x=params.getDouble("s2x",2);
	spline[2].y=params.getDouble("s2y",2);

	spline[3].x=params.getDouble("s3x",3);
	spline[3].y=params.getDouble("s3y",3);

	GetSplineCoefs(spline, coefs);
}
void addParams(InitInfo &params)
{
	params.format.precision=2;
	params.format.exposant=FALSE;

	params.addDouble("s0x",spline[0].x);
	params.addDouble("s0y",spline[0].y);
	
	params.addDouble("s1x",spline[1].x);
	params.addDouble("s1y",spline[1].y);

	params.addDouble("s2x",spline[2].x);
	params.addDouble("s2y",spline[2].y);

	params.addDouble("s3x",spline[3].x);
	params.addDouble("s3y",spline[3].y);
}
/**/
/* --------------------------------- INTERPOLATION
#define DEF_START 0
#define DEF_END 1
float a0 = 0, a1 = 0, a2 = 1, a3 = 1;
float y0 = 0, y1 = 0, y2 = 1, y3 = 1;
inline void func()
{
	x = t;
	y = a0 + t * (a1 + t * (a2 + t * a3 ));
}
void getParams(InitInfo &params)
{
	y0=params.getDouble("y0",0);
	y1=params.getDouble("y1",0);
	y2=params.getDouble("y2",1);
	y3=params.getDouble("y3",1);

	float f0 = y1;
	float f1 = y2;

	float df0 = (y2-y0)/2;
	float df1 = (y3-y1)/2;

	a0 = f0;
	a1 = df0;
	a2 = -3*f0 + 3*f1 - 2*df0 - df1;
	a3 = 2*f0 - 2*f1 + df0 + df1;
	
}
void addParams(InitInfo &params)
{
	params.format.precision=2;
	params.format.exposant=FALSE;
	params.addDouble("y0",y0);
	params.addDouble("y1",y1);
	params.addDouble("y2",y2);
	params.addDouble("y3",y3);
}
/**/
/*
#define DEF_START 0
#define DEF_END 1
inline void func()
{
	x = t;
	y = (( 1 + sin( (PI/2) * ( 1 - 2 * x ) ) ) / 2);
}
void getParams(InitInfo &params)
{
	
}
void addParams(InitInfo &params)
{
}
/**/
//--------------------------------------------------------------

class Func:public FlatApplication
{
	int Ni,dN,index,capt,coefCount;
	double rv,iv,arg,error,start,end;
	Vector<double> v[ID_SIZE], lsm;
	Font fontSmall;
	FlatText tbStart,tbEnd,tbPoints,tbParams,tbLSM;
	FlatButton bInterval,bPoints,bCapture,bParams,bLSM,bPlus,bMinus;
	FlatGraph fg;
	Interpolator interpolator;
	Channel *chnl[ID_COUNT];

	BOOL create();
	BOOL listen(WORD id, WORD code, const void *info);

	void buildData();
	BOOL setPoints(int points);
	void setInterval(double start, double end);
	void reshape();
	void setParams(CSTR params = "");
	void destroy();
	void setLSMCoefs(int coefs);
	BOOL calcLSM();
	void buildLSMGraph();
};

BOOL Func::calcLSM()
{
	if(	!N || !lsm.size || !getLSMPolyCoef(lsm.elems,lsm.size,v[ID_Y].elems,v[ID_X].elems,N) )
		return FALSE;
	PRINT("\nlsm:\n");
	for(int i=0;i<lsm.size;i++)
		PRINT("[%d] = %f\n", i, lsm[i]);
	buildLSMGraph();
	return TRUE;
}

void Func::buildLSMGraph()
{
	for(int i=0;i<N;i++)
	{
		double x = v[ID_X].elems[i];
		double y = funcValue(x, lsm.elems, lsm.size);
		v[ID_LSM].elems[i] = y;
		v[ID_ERR].elems[i] = y - v[ID_Y].elems[i];
	}
	fg.graph.update();
}

void Func::setLSMCoefs(int coefs)
{
	if(coefs<2)
		coefs=2;
	coefCount=coefs;
	tbLSM.setText(format.toString(coefs));
	lsm.setSize(coefs);
	calcLSM();
}

void Func::reshape()
{
	FlatApplication::reshape();
	fg.size.set(float(window.width-20),float(window.height-80));
	container.build();
}
BOOL Func::setPoints(int points)
{
	if(points<1)
		return FALSE;
	for(int i=0;i<ID_SIZE;i++)
	{
		if(!v[i].setSize(points))
		{
			setPoints(N);
			return FALSE;
		}
		chnl[i]->setSource(v[i]);
	}
	chnl[ID_F]->setSource(v[ID_Y]);
	N=points;
	tbPoints.setInt(N);
	return TRUE;
}
void Func::buildData()
{
	if(N<2)
		return;
	double step=(end-start)/(N-1);
	for(n=0;n<N;n++)
	{
		t=start+n*step;
		v[ID_T].elems[n]=t;
		func();
		v[ID_X].elems[n]=x;
		v[ID_Y].elems[n]=y;
	}
	calcLSM();
}
void Func::setInterval(double start, double end)
{
	this->start=start;
	this->end=end;
	tbStart.setDouble(start);
	tbEnd.setDouble(end);
	buildData();
}
void Func::setParams(CSTR params)
{
	InitInfo info_params;
	info_params.readStr(params,_T("("),_T(")"));
	getParams(info_params);
	info_params.destroy();
	addParams(info_params);
	tbParams.setText(info_params.writeStr(_T("("),_T(")")));
}
BOOL Func::listen(WORD id, WORD code, const void *info)
{
	
	if(id==bInterval.id||id==tbStart.id||id==tbEnd.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			setInterval(	format.toDouble(tbStart.text),
							format.toDouble(tbEnd.text));
			buildData();
		}
	}
	else if(id==bPoints.id||
			id==tbPoints.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			setPoints(format.toInt(tbPoints.text));
			buildData();
		}
	}
	else if(id==bParams.id||
			id==tbParams.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			setParams((tbParams.text));
			buildData();
		}
	}
	else if(id==bCapture.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			Image image;
			String str=_T("capture_");
			str+=format.toString(++capt);
			screenshot(&image,fg.graph.getRect(),0,-1,1);
			writeJPEG(str+_T(".jpg"),&image);
			File file;
			file.open(str+_T(".plt"),_T("wb"));
			fg.graph.save(file,TRUE);
		}
	}
	else if(id==bLSM.id||id==tbLSM.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			setLSMCoefs(format.toInt(tbLSM.getText(),4));
		}
	}
	else if(id==bPlus.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			setLSMCoefs(coefCount+1);
		}
	}
	else if(id==bMinus.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			setLSMCoefs(coefCount-1);
		}
	}
	else
	{
		return FALSE;
	}
	return TRUE;
}
BOOL Func::create ()	
{
	FlatApplication::create();
	name=_T("Func");

	display.width=800;
	display.height=600;

	format.precision=3;

	capt=0;

	fontSmall.setFace(_T("Courier New"));
	fontSmall.height=-12;
	add(&fontSmall);

	bInterval.text=_T("Int");
	bInterval.borderLine=1;
	bInterval.borderColor=INT_GRAY;
	bInterval.overColor=INT_WHITE;
	bInterval.pressColor=INT_WYELLOW;
	bInterval.activeLine=2;
	bInterval.font=&fontSmall;
	bInterval.fontColor=INT_BLACK;
	bInterval.margin.set(10,1);
	bInterval.bgColor=0xffdddddd;
	bInterval.location.set(10,10);

	tbStart.borderLine=1;
	tbStart.borderColor=INT_WGRAY;
	tbStart.activeLine=2;
	tbStart.font=&fontSmall;
	tbStart.fontColor=INT_BLACK;
	tbStart.margin.set(2,1);
	tbStart.size.set(85,20);
	tbStart.stick(DIR_RIGHT,5);
	tbStart.format.precision=3;

	tbEnd.copy(tbStart);
	tbEnd.stick(DIR_RIGHT,5);

	bPoints.copy(bInterval);
	bPoints.text=_T("Points");
	bPoints.stick(DIR_RIGHT,5);

	tbPoints.copy(tbStart);
	tbPoints.size.x=45;
	tbPoints.stick(DIR_RIGHT,5);

	bLSM.text=_T("LSM");
	bLSM.copy(bInterval);
	bLSM.stick(DIR_RIGHT,5);

	tbLSM.text=_T("5");
	tbLSM.copy(tbPoints);
	tbLSM.size.x=30;
	tbLSM.stick(DIR_RIGHT,5,0);

	bPlus.text=_T("+");
	bPlus.copy(bLSM);
	bPlus.stick(DIR_RIGHT,5,0);

	bMinus.text=_T("-");
	bMinus.copy(bPlus);
	bMinus.stick(DIR_RIGHT,5,0);

	bParams.copy(bInterval);
	bParams.text=_T("Params");
	bParams.stick(DIR_DOWN,0,10,&bInterval);

	tbParams.copy(tbStart);
	tbParams.size.x=500;
	tbParams.stick(DIR_RIGHT,5);

	bCapture.copy(bInterval);
	bCapture.text=_T("Capture");
	bCapture.stick(DIR_RIGHT,5);

	fg.bgColor=INT_GRAY;
	fg.graph.clear();
	fg.graph.grid.set(3,5);
	fg.graph.margin=10;
	fg.graph.marginChannel.set(15,50);
	fg.graph.font=&fontSmall;
	fg.graph.smooth=TRUE;
	fg.graph.formatX.precision=3;
	fg.graph.formatY.precision=3;
	fg.margin.set(10,10);
	fg.font=&fontSmall;
	fg.fontColor=INT_BLACK;
	fg.stick(DIR_DOWN,0,10,&bParams);
	
	add(&bInterval);
	add(&tbStart);
	add(&tbEnd);
	add(&bCapture);
	add(&bPoints);
	add(&tbPoints);
	add(&bLSM);
	add(&tbLSM);
	add(&bPlus);
	add(&bMinus);
	add(&bParams);
	add(&tbParams);
	add(&fg);
	
	chnl[ID_T]=fg.graph.addChannel(_T("arg"));
	chnl[ID_X]=fg.graph.addChannel(_T("x"),_T("arg"),INT_DGREEN,0,NULL,1.0f,TRUE,TRUE);
	chnl[ID_Y]=fg.graph.addChannel(_T("y"),_T("arg"),INT_DMAGENTA,0,NULL,1.0f,TRUE,TRUE);
	chnl[ID_LSM]=fg.graph.addChannel(_T("lsm"),_T("x"),INT_DCYAN,0,NULL,1.0f,TRUE,TRUE);
	chnl[ID_ERR]=fg.graph.addChannel(_T("err"),_T("x"),INT_DRED,0,NULL,1.0f,TRUE,TRUE);
	chnl[ID_F]=fg.graph.addChannel(_T("f"),_T("x"),INT_BLUE,0,NULL,1.0f,FALSE,TRUE);

	InitInfo info;
	info.read();
	window.left=info.getInt(_T("left"),window.left);
	window.top=info.getInt(_T("top"),window.top);
	window.width=info.getInt(_T("width"),window.width);
	window.height=info.getInt(_T("height"),window.height);
	resetWindow();

	setPoints(info.getInt(_T("points"),DEF_POINTS));
	setInterval(
		info.getDouble(_T("start"),DEF_START),
		info.getDouble(_T("end"),DEF_END));
	setParams(info.get(_T("params"),_T("")));
	setLSMCoefs(info.getInt(_T("lsm"),4));

	buildData();

	return TRUE;
}

void Func::destroy ()	
{
	InitInfo info;
	info.addInt(_T("left"),window.left);
	info.addInt(_T("top"),window.top);
	info.addInt(_T("width"),window.width);
	info.addInt(_T("height"),window.height);

	info.addInt(_T("points"),N);
	info.addDouble(_T("start"),start),
	info.addDouble(_T("end"),end);
	info.add(_T("params"),tbParams.text);
	info.addInt(_T("lsm"), coefCount);

	info.write();
	FlatApplication::destroy();
}

}; // namespace Lio

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	Lio::Func fo;
	return fo.main(hInstance,hPrevInstance,lpCmdLine,nCmdShow);
}

